/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package reasoner;

import DataTypes.AutoDiaryDataTypes;
import DataTypes.AutoDiaryDataTypes.AmbiAct;
import DataTypes.AutoDiaryDataTypes.AmbiLiteral;
import DataTypes.AutoDiaryDataTypes.ClearActLog;
import DataTypes.AutoDiaryDataTypes.PlaceLog;
import DataTypes.AutoDiaryDataTypes.RelationGroup;
import DataTypes.AutoDiaryDataTypes.WeightMatrix;
import DbPort.DataReceiver;
import DbPort.DataWindowManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 *
 * @author thang
 * Class: Weak Constraint Window Reasoner
 * function: 
 * - get result (best model) from dailyact reasoner with violation cost in a text file
 * - put result in a window of 5
 * - calculate dominant activities (when window is fully loaded)
 * - slide window 3 results
 * ---------------------------------------------------------------------------------------
 * JUST IMPLEMENT WITHOUT DISJUNTION (NO v IN RULES' HEAD), HAVE 1 ANSWER SET AT A TIME
 * ---------------------------------------------------------------------------------------
 * general case will be done later
 */

public class WindowReasoner {
    private static final String RESULT_FILE_NAME = "/home/thang/Study/Projects/diaryreasoner/result.txt";
    private static final String ACTIVITY_KEY = "basicAct(";
    private static final String ACTIVITY_END_KEY = ")";
    
    private static final String COST_KEY = "Cost ([Weight:Level]):"; 
    private static final String COST_VALUE_0 = "<0>"; 
    private static final String COST_VALUE_KEY = "<[";
    private static final String COST_SEPARATE_KEY = ":";
    
    // for get device
    private static final String DEVICE_ID_KEY = "device(";
    private static final String DEVICE_ID_END_KEY = ")";

    // for get clear act
    private static final String CLEAR_ACT_KEY = "complexAct(";
    
    // for get known place
    private static final String USER_AT_PLACE_KEY = "userAtPlaceCode(";
    private static final String END_KEY = ")";
    
    private static Scanner is = null;
    
    // open the result.txt file
    // get best models and their violation cost
    // store in window (in d_actWindow table)
    /*
        * -----------------------------------------------------------------------------
        * There are two ambiguous literals: activity and place (or code and place_id)
        * -----------------------------------------------------------------------------
        */

    // if window is fully loaded -> perform window reasoning
    // -> then store result2 in table d_simpleAct (this table normally have 1 record ?)
    // then complex Activities reasoner query d_simpleAct to reason complex activity
    // then store result in d_actLog for further reasoning (temporal, spatial...)
    
    
    /*
     * perform ambiguous reasoning
     * input: + weightMatrix is two dimenssions array
     * each row contains ambiguous literal and there weight
     * ambiliterals.length return number of ambiguous literals
     * + totalWeight: remember it is total weight of all answer sets in the window,
     *  not of all ambiguous literals because one answer set may have more than one ambiguous literal
     * ------------------------------------------------
     * - This function will be called by DataWindowManager when the window is fully filled
     */
    public static void reasoning(WeightMatrix wMatrix){
        // window reasoning
        
        showMatrix(wMatrix);
                
        // check null
        if ( wMatrix.matrix == null ) { return; }
        if ( wMatrix.matrix.isEmpty() ){ return; }
        
        // caculate member values
        memberFunction(wMatrix);
        
        // classify activities into groups of elimination
        ArrayList<RelationGroup> groups = new ArrayList<RelationGroup>();
        classifyIntoGroups(wMatrix, groups);
        
        // pick up the dominant ones
        ArrayList<AmbiLiteral> dominants = new ArrayList<AmbiLiteral>();
        dominants = selectDominants( groups );
        
        WeightMatrix result = new WeightMatrix();
        result.device_id = wMatrix.device_id;
        result.time = wMatrix.time;
        result.windowWeight = wMatrix.windowWeight;
        result.matrix = new ArrayList<AmbiLiteral>();
        result.matrix = dominants;
        // store to db and CALL COMPLEX SITUATION REASONING
        storeToDb(result);
        
        // slide window
        DataWindowManager.slideWindow(autodiaryserver.Declare.WINDOW_SLIDES);
        
        // call complex reasoning
        
    }
    
    public static void memberFunction( WeightMatrix wMatrix ){
        // check null
        if ( wMatrix == null || wMatrix.matrix.isEmpty() || wMatrix.matrix == null ){ return; }
        
        for ( AmbiLiteral a: wMatrix.matrix ){
            // convert in to double to devide
            double dw = a.weight;
            double dt = wMatrix.windowWeight;
            
            a.memberValue = (dw / dt);
        }

    }

    
    // this procedure just for test 
    public static void showMatrix(WeightMatrix wMatrix){

        System.out.println( "WEIGHT MATRIX" );
        System.out.println(wMatrix.device_id);
        System.out.println(wMatrix.time);
        System.out.println(wMatrix.windowWeight);
        if ( wMatrix.matrix != null ) {
            for ( DataTypes.AutoDiaryDataTypes.AmbiLiteral al: wMatrix.matrix){
                System.out.print(al.code);
                System.out.println(": " + al.weight);
            }
        }
        
        System.out.println();
        
    }
    
    public static void storeToDb(WeightMatrix result){
        
        DbPort.DataReceiver.receiveData(result);
        
        /*
        // just check null
        if ( result == null || result.matrix == null || result.matrix.isEmpty() ) { return; }
        
        System.out.println( "RESULT " );
        System.out.println("Device_id: " + result.device_id );
        System.out.println("Time: " + result.time );
        System.out.println("Size: " + result.matrix.size());
        for ( AmbiLiteral a: result.matrix ){
            System.out.println( "Code: " + a.code );
            System.out.println( "Weight: " + a.weight );            
            System.out.println( "Member value: " + a.memberValue );
            System.out.println(  );
        }
        * 
        */
    }
    
    public static ArrayList<AmbiLiteral> selectDominants(ArrayList<RelationGroup> groups){
        ArrayList<AmbiLiteral> result = new ArrayList<AmbiLiteral>();
        
        // just check empty
        if ( groups.isEmpty() || (groups == null) ){ return null; }
                
        // for each eliminated literals group
        for ( RelationGroup g: groups ){
            // just check empty
            if ( g.group.isEmpty() || (g.group == null) || (g == null) ){ break; }
        
            // check it is eliminated group, just make sure
            if ( g.relation.equalsIgnoreCase(autodiaryserver.Declare.ACT_REL_ELIMINATION)){
                // default dominant literal is the first one
                int maxIndex = 0;

                // compare member value to find dominant literal
                for ( int i = 1; i < g.group.size(); i++ ){
                    if ( g.group.get(i).memberValue >  g.group.get(maxIndex).memberValue ){
                        maxIndex = i;
                    }
                }
                
                // put the dominant to result
                result.add(g.group.get(maxIndex));
            }
        }
        
        return result;
    }
    
    public static void classifyIntoGroups(WeightMatrix wMatrix, ArrayList<RelationGroup> groups){
        // check null
        if ( wMatrix.matrix == null ){ return; }
        if ( wMatrix.matrix.isEmpty() ){ return; }
        
        // init groups
        if ( groups == null ){ groups = new ArrayList<RelationGroup>(); }
        if ( !groups.isEmpty() ){ groups.clear(); }
        
        // current group
        //int currentGroup = 0;
        
        while (true) {
            // initial new group
            RelationGroup gr = new RelationGroup();
            gr.relation = autodiaryserver.Declare.ACT_REL_ELIMINATION;
            gr.group = new ArrayList<AmbiLiteral>();
            
            // get the first literal
            //AmbiLiteral currentLiteral = wMatrix.matrix.get(0);
            // compare to all other in the list
            
            // for every first literal, looking all eliminated ones
            forEachFirstLiteral: // label for break command
            while (true){
                // if there are more than one literal then looking all eliminated ones
                if( wMatrix.matrix.size() > 1 ){ 
                    // for every other literal in the list
                    for ( int i = 1; i < wMatrix.matrix.size(); i++ ){
                        // if have eliminate relation with the first literal
                        // then put in current group, delete from original list
                        if ( eliminated(wMatrix.matrix.get(0), wMatrix.matrix.get(i)) ){
                            gr.group.add(wMatrix.matrix.get(i));
                            wMatrix.matrix.remove(i);
                            // start again to find other eliminance
                            break;
                        }
                        // if search to last literal then finish this first literal
                        if( i == (wMatrix.matrix.size() -1) ){break forEachFirstLiteral;}
                    }
                }else { break; }
            }
            
            // put the first literal into current group
            gr.group.add(wMatrix.matrix.get(0));
            // add current group to the groups
            groups.add(gr);

            // delete the first literal from original list
            wMatrix.matrix.remove(0);

            // finish
            if( wMatrix.matrix.isEmpty() ){ break; }
        }
    }

    /*
     * these relations should be query from db
     * now just hard code
     */
    public static boolean eliminated( AmbiLiteral a, AmbiLiteral b){
        boolean result = false;
        
        // check if the same literal
        if( a.code.equalsIgnoreCase(b.code) ) { return false; }
        
        autodiaryserver.Declare.getRelationDB();
        
        // check the pair in each eliminated group
        for ( ArrayList<String> g:  autodiaryserver.Declare.eliminatedGroups){
            boolean foundA = false;
            boolean foundB = false;
            //if ( g.contains(a.code) && g.contains(b.code)) { result = true; }
            // use string compare for more robust
            for ( String l: g ) {
                if ( l.equalsIgnoreCase(a.code) ) { foundA = true; }
                if ( l.equalsIgnoreCase(b.code) ) { foundB = true; }
            }
            if ( foundA && foundB ) { result = true; }
        }
        //autodiaryserver
        //result = weight / windowWeight;
        
        return result;
    }
    
    //{ {"walking", "running", "driving", "stayingStill"}, {"indoor", "outdoor"} };    
    
    public static void putActToWindow(){
        
        // prepare database
        if ( !DbPort.Declare.connectDb ) {
            DbPort.ConnectionManager.connectDb();
        }
        
        // open the result.txt file
        is = mylib.FileHandler.openFile(RESULT_FILE_NAME);
        // get best models and their violation cost
        getAmbiAct();

        // close database
        //DbPort.ConnectionManager.disconnectDb();

    }

    /*
     * log place information if user is at a known place
     * 
     */
    public static void logKnownPlace(String line){
        int     time_int     = 0;
        String  strPlace     = "";
        int     type_id      = 0;
        long    strPId       = 0;
        //String sTime    = "";
        
        // reset index
        int offset = 0;
        int placeIndex = -1;
        List<AutoDiaryDataTypes.PlaceLog> pList = null;
        
        // search for all known places
        // userAtPlaceCode(506,20318,1337904718)
        while(true){
            // search string "activity("
            placeIndex = line.indexOf(USER_AT_PLACE_KEY, offset);

            // if not found exit
            if ( placeIndex < 0 ) { 
                break; 
            }

            if ( pList == null ) {
                pList = new ArrayList<AutoDiaryDataTypes.PlaceLog>();
            }
            
            
            offset = placeIndex + 1;  // to make sure search the next place

            // get string "activity([name], [time])". Do I need to do this?????
            strPlace = line.substring(placeIndex + USER_AT_PLACE_KEY.length(), line.indexOf(END_KEY, offset));

            // get indexes of the two comma
            int firstCommaIndex = strPlace.indexOf(",");
            int secondCommaIndex = strPlace.indexOf(",", firstCommaIndex + 1);
            
            // get place type            
            type_id = Integer.parseInt( strPlace.substring(0, firstCommaIndex) );

            // get place or gps id
            strPId = Long.parseLong( strPlace.substring(firstCommaIndex + 1, secondCommaIndex) );

            // get time
            time_int = Integer.parseInt( strPlace.substring( secondCommaIndex + 1 ) );

            // prepare object
            AutoDiaryDataTypes.PlaceLog np = new AutoDiaryDataTypes.PlaceLog();
            np.device_id = autodiaryserver.Declare.DEVICE_ID;
            np.type_id = type_id;
            np.time1_int = time_int;
            np.time2_int = time_int;
            
            pList.add(np);

            //System.out.println(strAct);
            //System.out.println(time_int);
        }    
        
        // prepare database
        if ( !DbPort.Declare.connectDb ) {
            DbPort.ConnectionManager.connectDb();
        }
        
        DataReceiver.receiveData(pList);
        
        // close database
        //DbPort.ConnectionManager.disconnectDb();

        // test
        if ( pList != null ) {
            System.out.println("User's known places: ");
            for ( PlaceLog p: pList ){
                System.out.println("row id: " + p.id );
                System.out.println(p.device_id);
                System.out.println(p.type_id );
                System.out.println(p.time1_int );
                System.out.println(p.time2_int);
            }
        }
    }

    /*
     * get device_id and update device_id global var autodiaryserver.Declare.DEVICE_ID
     * 
     */
    public static void getDeviceId(String line){
        int beginIndex = line.indexOf( DEVICE_ID_KEY ) + DEVICE_ID_KEY.length();
        // check null
        if( beginIndex < 0 ){ return; }
        
        int endIndex = line.indexOf(DEVICE_ID_END_KEY, beginIndex);
        autodiaryserver.Declare.DEVICE_ID = line.substring(beginIndex, endIndex);
    }

    /*
     * log place information if user is at a known place
     * 
     */
    public static void logClearAct(String line){
        int     time_int     = 0;
        String  strActs     = "";
        String  code         = "";
        //String sTime    = "";
        
        // reset index
        int offset = 0;
        int actIndex = -1;
        List<AutoDiaryDataTypes.ClearActLog> cList = null;
        
        // search for all known places
        // userAtPlaceCode(506,20318,1337904718)
        while(true){
            // search string "activity("
            actIndex = line.indexOf(CLEAR_ACT_KEY, offset);

            // if not found exit
            if ( actIndex < 0 ) { 
                break; 
            }

            if ( cList == null ) {
                cList = new ArrayList<AutoDiaryDataTypes.ClearActLog>();
            }
            
            
            offset = actIndex + 1;  // to make sure search the next place

            // get string "activity([name], [time])". Do I need to do this?????
            strActs = line.substring(actIndex + CLEAR_ACT_KEY.length(), line.indexOf(END_KEY, offset));

            // get indexes of the two comma
            int firstCommaIndex = strActs.indexOf(",");
            //int secondCommaIndex = strPlace.indexOf(",", firstCommaIndex + 1);
            
            // get actCode            
            code = strActs.substring(0, firstCommaIndex);

            // get time
            time_int = Integer.parseInt( strActs.substring( firstCommaIndex + 1 ) );

            // prepare object
            AutoDiaryDataTypes.ClearActLog ca = new AutoDiaryDataTypes.ClearActLog();
            ca.device_id = autodiaryserver.Declare.DEVICE_ID;
            ca.code = code;
            ca.time1_int = time_int;
            ca.time2_int = time_int;
            
            cList.add(ca);

            //System.out.println(strAct);
            //System.out.println(time_int);
        }    
        
        // prepare database
        if ( !DbPort.Declare.connectDb ) {
            DbPort.ConnectionManager.connectDb();
        }
        
        DataReceiver.receiveClearActLog(cList);
        
        // close database
        //DbPort.ConnectionManager.disconnectDb();

        // test
        if ( cList != null ) {
            System.out.println("User's activity: ");
            for ( ClearActLog c: cList ){
                System.out.println("row id: " + c.id );
                System.out.println(c.device_id);
                System.out.println(c.code );
                System.out.println(c.time1_int );
                System.out.println(c.time2_int);
            }
        }
    }
    
    /*
     * get result from dailyact reasoner (reasoning level 1) and store in db
     * 
     */
    public static void getAmbiAct(){
        // get best models and their violation cost
        // store in window (in d_simpleActWindow table)
        // check if window is fully loaded -> perform window reasoning
        // -> then store result2 in table d_simpleAct
        // get every line from is
        String device_id = autodiaryserver.Declare.DEVICE_ID;
        //String device_id = autodiaryserver.Declare.DEVICE_ID;
        String line = "";
        String strAct = "";
        int     time_int = 0;
        int cost = -1;
        boolean newList = true;
        List<AutoDiaryDataTypes.AmbiAct> al = null;
        
        
        while ( is.hasNextLine() ){
            line = is.nextLine();
            System.out.println(line);
            
            // reset index
            int offset = 0;
            int actIndex = -1;
            int costIndex = -1;
            
            if ( newList ) {
                al = new ArrayList<AutoDiaryDataTypes.AmbiAct>();            
            }
            
            // search for all activities
            // activity([name], [time])
            while(true){
                // search string "activity("
                actIndex = line.indexOf(ACTIVITY_KEY, offset);
                
                // if not found exit
                if ( actIndex < 0 ) { 
                    // mark that finish get activity, get cost at next line
                    newList = false; // or can empty the list?
                    break; 
                }
                
                // get position, clear activity only once per line (best model)
                if( offset == 0 ) {
                    WindowReasoner.getDeviceId(line);
                    WindowReasoner.logKnownPlace(line);
                    WindowReasoner.logClearAct(line);
                }
                
                offset = actIndex + 1;  // to make sure search the next activity
                
                // get string "activity([name], [time])". Do I need to do this?????
                strAct = line.substring(actIndex + ACTIVITY_KEY.length(), line.indexOf(ACTIVITY_END_KEY, offset));
                
                // get time
                time_int = Integer.parseInt( strAct.substring( strAct.indexOf(",") + 1 ) );
                // get activity name
                strAct = strAct.substring(0, strAct.indexOf(","));
                
                // prepare object
                AmbiAct aa = new AmbiAct();
                aa.setDevice_id(device_id);
                aa.setActivityCode(strAct);
                aa.setSensorTimeStamp(time_int);
                al.add(aa);
                
                //System.out.println(strAct);
                //System.out.println(time_int);
            }
            
            // search for violation cost
            costIndex = line.indexOf(COST_KEY);
            // if cost line then search for cost value
            if (costIndex >= 0){
                // if cost = 0
                // Cost ([Weight:Level]): <0>
                int index0 = line.indexOf(COST_VALUE_0, costIndex);
                if ( index0 >= 0 ) {
                    cost = 0;
                }
                
                // if cost > 0
                // Cost ([Weight:Level]): <[1:1]>
                int indexWeight = line.indexOf(COST_VALUE_KEY, costIndex);
                if ( indexWeight >= 0 ){
                    String strCost = line.substring(indexWeight + COST_VALUE_KEY.length(), line.indexOf(COST_SEPARATE_KEY, indexWeight));
                    cost = Integer.parseInt(strCost);
                }
                
                // add weight
                for ( AmbiAct a: al){
                    a.setWeight(cost);
                }
                
                //for test
                /*
                System.out.println("Size " + al.size() );
                for( AutoDiaryDataTypes.AmbiAct a: al){
                    System.out.println( "Device id: " + a.getDevice_id() );
                    System.out.println( "Code: " + a.getActivityCode() );
                    System.out.println( "Weight: " + a.getWeight() );
                    System.out.println( "Time: " + a.getSensorTimeStamp() );
                    System.out.println(  );
                }
                * 
                */
                
                // put act in window --------------------------------
                DataWindowManager.insertData(al);

                // create new list for next best model (in case there are more than one best model - or answerset)
                newList = true;
            }
        }
    }

    public static void windowReasoning(){
        // get best models and their violation cost
        // store in window (in d_simpleActWindow table)
        // check if window is fully loaded -> perform window reasoning
        // -> then store result2 in table d_simpleAct
        // then complex Activities reasoner query d_simpleAct to reason complex activity
        // then store result in d_actLog for further reasoning (temporal, spatial...)
    }

    
}
